Inversion of Control en Dependency Injection in .NET Core
Inversion of Control (IoC) en Dependency Injection (DI) bieden een eenvoudig mechanisme om objecten onafhankelijk van andere objecten te maken. Wanneer een object andere objecten nodig heeft, zijn die objecten afhankelijk van elkaar.
- IoC biedt diensten (services) waarmee objecten andere objecten kunnen aanspreken zonder er afhankelijk van te zijn.
- Dependency Injection is een manier om inversion of control te bereiken. We leren in dit lesonderdeel wat dependency injection is we laten zien hoe DI in de praktijk werkt.
Bronnen
- Christos Matskas, .NET Core Dependency Injection with constructor parameters, 04 July 2017
- Chad Ramos, Dependency Injection, Logging and Configuration In A .NET Core Console Application, Feb 2, 2017
- Mark Michaelis, Essential .NET - Dependency Injection with .NET Core, June 2016
- Andrew Lock, Using dependency injection in a .Net Core console application, 8 november 2016
Inleiding
Mens spreekt van een afhankelijkheid als een klasse een externe klasse nodig heeft om zijn werk te kunnen doen. Net zoals ik, wanneer ik dit lesmateriaal intyp, afhankelijk ben van bijvoorbeeld mijn computer. Verwijder de computer en ik zou het lesmateriaal niet kunnen voltooien.
De afhankelijkheden injecteren (dependency injection) in de klasse, die ze nodig heeft, is een goede manier om onze klassen te ontkoppelen. Alhoewel elke klasse zelf een nieuw object kan instanciëren en toegang heeft tot de methoden en eigenschappen ervan, is daarom niet zo dat die klasse daar thuis hoort. Het injecteren van een klassenafhankelijkheid is een goede manier om het single responsability patroon toe te passen, Dit patroon bevordert een goed objectgericht ontwerp. Dit principe houdt in dat elke klasse verantwoordelijkheid is voor een specifiek deel van de functionaliteit die door de applicatie wordt geleverd, en dat die verantwoordelijkheid volledig door de klasse moet worden ingekapseld.
Wat is DI?
Probleemstelling
Inversion of Control (IoC) en Dependency Injection (DI) zijn twee gerelateerde maar verschillende patronen.
Het IoC patroon leert ons dat objecten niet afhankelijk mogen zijn van werkelijke, concrete, klassen, maar van abstracte basisklassen of interfaces die de functionaliteit bepalen die we nodig hebben. Afhankelijk van hoe die klassen geregistreerd zijn retourneert het IoC-framework een concrete klasse die overeenkomt met de gewenste interface of abstracte basisklasse.
DI, aan de andere kant, verwijst naar de manier waarop een concrete klasse is gebouwd: de benodigde objecten waarvan de klasse afhankelijk is, worden doorgegeven aan de constructor (constructor injectie, hoewel er andere opties zijn).
Deze twee patronen gaan goed samen, en het kan soms verwarrend zijn omdat die twee patterns vaak door elkaar gebruikt worden.
.NET heeft sinds het prille begin een beperkte vorm van inversion of control ondersteund. Maar in .NET Core heeft Microsoft het een centrale plaats gegeven en is bijna alles afhankelijk van het inversion-of-control- en van de dependency-injectionframework.
Met een Inversion of Control- en een dependency Injectionframework kan je diensten (services, die eigenlijk concrete klassen zijn) registreren en ze ter beschikking stellen aan andere klassen door middel van een een abstracte basisklasse of een interface die ze implementeren. De applicatiecode die van deze klassen afhankelijk is hoeft zich geen zorgen te maken over wat de werkelijke klasse precies doet die het contract implementeert.
Dit maakt het gemakkelijk om de werkelijke afhankelijkheden in configuratie of at runtime te wijzigen.
Daarnaast injecteert het ook afhankelijkheden in de werkelijke klassen die het opbouwt.
Design
We vertrekken van het volgende scenario. We hebben een Postcodeapp nodig. Met app kunnen we een een postcode ingeven en de naam van de stad opzoeken of omgekeerd de stad opgeven en den postcode opzoeken. En zeg niet dat dit een te simpele app is!
We hebben echter drie verschillende mogelijkheden om de postcodes op te slaan:
- CSV
- XML
- JSON
In het DI jargon heet het dat we deze service (datastorage voor postcodes) drie mogelijke leveranciers hebben.
We willen niet dat onze app slechts met één provider kan werken. We willen dus niet dat de connectie met de datastore hardgecodeerd is in de app.
DI voorziet een extra niveau waarmee indirect een service kan implementeren. In plaats van de service direcht met de new
operator te instanciëren zal de app aan een service-verzameling of aan een "factory" naar dat object vragen.
Ten tweede zal de app aan de service-collectie of factory niet naar een concreet type object vragen maar naar een interface die door de service-collectie of factory geïmplementeerd zal worden.
Het patroon van ontkoppeling van het feitelijke exemplaar dat wordt teruggegeven aan de client heet Inversion of Control. Het is niet de cliënt die bepaalt wat er geïnstancieerd wordt, wat gebeurd als de constructor van de klasse expliciet een object maakt met de new
operator, maar de DI. DI registreert een associatie tussen het type dat door de cliënt wordt aangevraagd (meestal een interface) en het type dat wordt teruggestuurd. Bovendien bepaalt DI in het algemeen de levensduur van het type dat wordt geretourneerd:
- één instantie van het object doorheen heel de app (singleton)
- een nieuwe instantie voor elke aanvraag
- of iets tussenin
Het aanbieden van een instantie van de 'service', in plaats van dat de client zelf een instantie maakt, is het fundamentele principe van DI.
Sommige DI-frameworks maken een ontkoppeling van de host mogelijk door een bindend mechanisme te ondersteunen dat gebaseerd is op configuratie en reflectie, in plaats van op compile-time binding. Deze ontkoppeling staat bekend als het service locator patroon.
Oplossing
We herstructuren de PostcodeApp door Dependency Injection toe te passen: Refactoring PostcodeApp - DI toepassen.